﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Net;
using System.Net.Sockets;

namespace serwer {
    /// <summary>
    /// Nasłuchuje na wybranym porcie i obsługuje przychodzące połączenia.
    /// </summary>
    class Server {
        /// <summary>
        /// Port, na którym nasłuchuje serwer.
        /// </summary>
        public int Port {
            get {
                return port;
            }
        }
        /// <summary>
        /// Maksymalna liczba obsługiwanych jednocześnie połączeń.
        /// </summary>
        public int MaxClientNumber {
            get {
                return maxClientNumber;
            }
        }
        /// <summary>
        /// Maksymalna liczba połączeń oczekujących na przyjęcie.
        /// </summary>
        public int MaxPendingConnections {
            get {
                return maxPendingConnections;
            }
        }
        /// <summary>
        /// Zwraca true, jeżeli serwer aktualnie nasłuchuje, false w przeciwnym wypadku.
        /// </summary>
        public bool IsListening {
            get {
                if (listeningSocket == null) return false;
                else return true;
            }
        }

        /// <summary>
        /// Socket używany przez serwer do nasłuchiwania.
        /// </summary>
        private Socket listeningSocket;
        /// <summary>
        /// Lista połączonych klientów.
        /// </summary>
        private List<Client> clientList;
        /// <summary>
        /// Wątek nasłuchujący i przyjmujący przychodzące połączenia.
        /// </summary>
        private Thread listeningThread;
        /// <summary>
        /// Maksymalna liczba obsługiwanych jednocześnie połączeń.
        /// </summary>
        private readonly int port;
        /// <summary>
        /// Maksymalna liczba obsługiwanych jednocześnie połączeń.
        /// </summary>
        private readonly int maxClientNumber;
        /// <summary>
        /// Maksymalna liczba klientów oczekujących na połączenie.
        /// </summary>
        private readonly int maxPendingConnections;
        /// <summary>
        /// Event ustawian, gdy na liście klientów pojawi się wolne miejsce.
        /// </summary>
        private readonly ManualResetEventSlim freeSpaceInQueueEvent;

        /// <summary>
        /// Domyślna maksymalna liczba połączonych klientów.
        /// </summary>
        private const int defaultMaxClientNumber = 10;
        /// <summary>
        /// Domyślna maksymalna liczba klientów oczekujących na połączenie.
        /// </summary>
        private const int defaultMaxPendingConnections = 5;
        /// <summary>
        /// Czas na wysłanie pozostałych danych przy zakończeniu połączenia.
        /// </summary>
        private const int closeTimeout = 100;
        
        /// <summary>
        /// Tworzy nowy obiekt klasy Server nasłuchujący na podanym porcie i o określonej maksymalnej liczbie obsługiwanych jednocześnie klientów.
        /// </summary>
        /// <param name="port">Port, na którym nasłuchiwać będzie serwer.</param>
        /// <param name="maxClientNumber">Maksymalna liczba połączonych klientów.</param>
        /// <param name="maxPendingConnections">Maksymalna liczba klientów oczekujących na połączenie.</param>
        public Server(int port, int maxClientNumber, int maxPendingConnections) {
            this.port = port;
            this.maxClientNumber = maxClientNumber;
            this.maxPendingConnections = maxPendingConnections;
            listeningSocket = null;
            freeSpaceInQueueEvent = new ManualResetEventSlim(true);
        }
        /// <summary>
        /// Tworzy nowy obiekt klasy Server nasłuchujący na podanym porcie i o domyślnej maksymalnej liczbie obsługiwanych jednocześnie klientów.
        /// </summary>
        /// <param name="port">Port, na którym nasłuchiwać będzie serwer.</param>
        public Server(int port) : this(port, defaultMaxClientNumber, defaultMaxPendingConnections) { }
        /// <summary>
        /// Rozpoczyna nasłuchiwanie i uruchamia wątek obsługujący przychodzące połączenia.
        /// </summary>
        /// <returns>True w przypadku sukcesu, false gdy serwer jest już uruchomiony.</returns>
        public bool Start() {
            if (IsListening) return false;
            clientList = new List<Client>();
            try {
                listeningSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                listeningSocket.Bind(new IPEndPoint(IPAddress.Any, Port));
                listeningSocket.Listen(MaxPendingConnections);
            }
            catch (SocketException ex) {
                throw new Exception("Port " + port + " jest zajęty.", ex);
            }
            listeningThread = new Thread(new ThreadStart(ListeningLoop));
            freeSpaceInQueueEvent.Set();
            listeningThread.Start();
            return true;
        }
        /// <summary>
        /// Zatrzymuje nasłuchiwanie i rozłącza wszystkich połączonych klientów.
        /// </summary>
        /// <returns>True w przypadku sukcesu, false gdy serwer jest już zatrzymany.</returns>
        public bool Stop() {
            if (!IsListening) return false;
            freeSpaceInQueueEvent.Set();
            listeningSocket.Close(closeTimeout);
            listeningThread.Join();            
            listeningSocket = null;
            for (int i = clientList.Count - 1; i >= 0; i--) {
                Client c = clientList[i];
                clientList.RemoveAt(i);
                c.CloseConnection();
            }
            clientList = null;
            return true;
        }
        /// <summary>
        /// Zwraca listę połączonych klientów ułożonych w wybranym porządku.
        /// </summary>
        /// <param name="order">Porządek według którego ułożona ma być lista.</param>
        /// <returns>Lista połączonych klientów.</returns>
        public List<ClientInformation> GetClientList(ClientListOrder order) {
            List<ClientInformation> clientInformationList = new List<ClientInformation>();
            lock (clientList) {
                foreach (Client c in clientList) {
                    clientInformationList.Add(new ClientInformation(c.ClientId, c.IpAddress, c.ConnectionTime));
                }
            }
            switch (order) {
                case ClientListOrder.IdAscending:
                    clientInformationList = clientInformationList.OrderBy(c => c.ClientID).ToList();
                    break;
                case ClientListOrder.IdDescending:
                    clientInformationList = clientInformationList.OrderByDescending(c => c.ClientID).ToList();
                    break;
                case ClientListOrder.IPAscending:
                    clientInformationList = clientInformationList.OrderBy(c => c.ClientIP).ToList();
                    break;
                case ClientListOrder.IPDescending:
                    clientInformationList = clientInformationList.OrderByDescending(c => c.ClientIP).ToList();
                    break;
                case ClientListOrder.TimeAscending:
                    clientInformationList = clientInformationList.OrderBy(c => c.ConnectionTime).ToList();
                    break;
                case ClientListOrder.TimeDescending:
                    clientInformationList = clientInformationList.OrderByDescending(c => c.ConnectionTime).ToList();
                    break;
                default:
                    break;
            }
            return clientInformationList;
        }
        /// <summary>
        /// Zwraca listę połączonych klientów ułożonych według czasu rozpoczęcia połączenia.
        /// </summary>
        /// <returns>Lista połączonych klientów.</returns>
        public List<ClientInformation> GetClientList() {
            return GetClientList(ClientListOrder.Default);
        }        
        /// <summary>
        /// Rozłącza i usuwa z listy klienta o określonym identyfikatorze.
        /// </summary>
        /// <param name="clientId">ID klienta do rozłączenia.</param>
        /// <returns>True w przypadku sukcesu, false gdy klienta o podanym ID nie ma na liście.</returns>
        public bool DisconnectClient(int clientId) {
            Client clientToDisconnect = null;
            lock (clientList) {
                foreach (Client c in clientList) {
                    if (c.ClientId == clientId) {
                        clientToDisconnect = c;
                        break;
                    }
                }
            }
            if (clientToDisconnect == null) return false;
            else {
                DisconnectClient(clientToDisconnect);
                return true;
            }
        }
        /// <summary>
        /// Rozłącza i usuwa z listy klienta o określonym adresie IP.
        /// </summary>
        /// <param name="clientIP">Adres IP kliena do rozłączenia.</param>
        /// <returns>True w przypadku sukcesu, false gdy klienta o podanym IP nie ma na liście.</returns>
        public bool DisconnectClient(string clientIP) {
            Client clientToDisconnect = null;
            lock (clientList) {
                foreach (Client c in clientList) {
                    if (c.IpAddress == clientIP) {
                        clientToDisconnect = c;
                        break;
                    }
                }
            }
            if (clientToDisconnect == null) return false;
            else {
                DisconnectClient(clientToDisconnect);
                return true;
            }
        }
        /// <summary>
        /// Rozłącza i usuwa z listy określonego klienta.
        /// </summary>
        /// <param name="client">Klient do rozłączenia.</param>
        private void DisconnectClient(Client client) {
            client.CloseConnection();
        }
        /// <summary>
        /// Pętla wątku nasłuchującego.
        /// </summary>
        private void ListeningLoop() {            
            while (true) {
                try {
                    freeSpaceInQueueEvent.Wait();
                    Client connectedClient = new Client(listeningSocket.Accept(), clientList, freeSpaceInQueueEvent);
                    lock (clientList) {
                        clientList.Add(connectedClient);
                        if (clientList.Count == maxClientNumber) freeSpaceInQueueEvent.Reset();
                    }                    
                }
                catch (SocketException) {
                    break;
                }
            }            
        }

    }
    /// <summary>
    /// Wartość określająca porządek listy klientów.
    /// </summary>
    public enum ClientListOrder : int {
        Default = 0,
        IdAscending = 1,
        IdDescending = 2,
        IPAscending = 3,
        IPDescending = 4,
        TimeAscending = 5,
        TimeDescending = 6,
    }
}










